use clap::{Parser, Subcommand, ValueEnum};
// use clap_serde_derive::ClapSerde;
// use serde::Serialize;
// use std::fs;
use std::net::IpAddr;
mod browser;
mod def;
mod image;
#[macro_use]
mod macros;
mod ocr;
mod output;
mod prequire;
mod project;

use self::image::ImageComands;
use def::SUPPORT_PACKAGES;
use dialoguer::{theme::ColorfulTheme, Input, Select};
use prequire::{
  adb::{serve_package, ADBComands, PackageComands},
  cos::{serve_cos, CosComands},
  imagemagick::{serve_image_magick, ImageMagickComands},
};

use project::{
  ProjectActionCommands, ProjectConfigCommands, ProjectDeviceCommands, ProjectScreenCommands,
  ProjectTrainCommands,
};

pub struct App {
  conf: Config,
}

impl App {
  pub fn new() -> Self {
    App {
      conf: Config::parse(),
    }
  }
  pub fn serve(&self) {
    match self.conf.command {
      Commands::Adb {
        adb_command: ref device_command,
      } => {
        prequire::adb::serve_device(device_command);
      }
      Commands::Cos { ref command } => {
        serve_cos(command);
      }
      Commands::Add { ref prequire } | Commands::Install { ref prequire } => {
        println!("install or add prequire {prequire:?}");

        let output = prequire::check_prequires(prequire);
        if !output {
          eprintln!("checking prequire <{:?}> failed ", prequire);
        }
      }
      Commands::Browse => {
        println!("browse");
        browser::browse_wikipedia().expect("browse_wikipedia err");
      }
      Commands::Check { ref prequire } => {
        println!("checking prequire <{prequire:?}>...");
        prequire::check_prequires(prequire);
      }
      Commands::Config { ref command } => {
        match project::Project::from_toml(&self.conf.project_dir) {
          Ok(mut proj) => {
            proj.serve_config_commands(command);
          }
          Err(_) => {
            eprintln!("`{}`无法找到hermes.toml", self.conf.project_dir)
          }
        }
      }
      Commands::Device { ref device_command } => {
        match project::Project::from_toml(&self.conf.project_dir) {
          Ok(mut proj) => {
            proj.serve_device_commands(device_command);
          }
          Err(_) => {
            eprintln!("`{}`无法找到hermes.toml", self.conf.project_dir)
          }
        }
      }
      Commands::Exec { ref action_command } => {
        match project::Project::from_toml(&self.conf.project_dir) {
          Ok(proj) => {
            proj.serve_action_commands(action_command);
          }
          Err(_) => {
            eprintln!("`{}`无法找到hermes.toml", self.conf.project_dir)
          }
        }
      }
      Commands::Image { ref image_command } => {
        self::image::serve(image_command);
      }
      Commands::Package {
        ref package_command,
      } => {
        serve_package(package_command);
      }
      Commands::Magick { ref command } => {
        serve_image_magick(command);
      }
      Commands::New => {
        let theme = &ColorfulTheme::default();
        let name: String = Input::with_theme(theme)
          .with_prompt("项目名称")
          .interact_text()
          .unwrap();

        let selection = Select::with_theme(theme)
          .with_prompt("目标app")
          .default(0)
          .items(&SUPPORT_PACKAGES[..])
          .interact()
          .unwrap();
        // println!("Enjoy your {}!", SUPPORT_PACKAGES[selection]);
        let description = Input::with_theme(theme)
          .with_prompt("项目描述")
          .default(String::from(""))
          .interact_text()
          .ok();

        project::Project::new(name, SUPPORT_PACKAGES[selection].to_string(), description);
      }
      Commands::Ocr => {
        println!("ocr");
        ocr::ocr_test();
      }
      Commands::Screen { ref screen_command } => {
        match project::Project::from_toml(&self.conf.project_dir) {
          Ok(proj) => {
            proj.serve_screen_commands(screen_command);
          }
          Err(_) => {
            eprintln!("`{}`无法找到hermes.toml", self.conf.project_dir)
          }
        }
      }
      Commands::Train { ref command } => {
        match project::Project::from_toml(&self.conf.project_dir) {
          Ok(proj) => {
            proj.serve_train_commands(command);
          }
          Err(_) => {
            eprintln!("`{}`无法找到hermes.toml", self.conf.project_dir)
          }
        }
      }
    }
  }
}

#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)] // Read from `Cargo.toml`
struct Config {
  #[arg(default_value_t = String::from("."), default_missing_value = ".")]
  project_dir: String,

  #[command(subcommand)]
  command: Commands,
}

#[derive(Debug, Subcommand)]
enum Commands {
  Adb {
    #[command(subcommand)]
    adb_command: ADBComands,
  },
  Cos {
    #[command(subcommand)]
    command: CosComands,
  },
  Add {
    #[arg(require_equals = true, value_enum)]
    prequire: prequire::Prequires,
  },
  Browse,
  Check {
    #[arg(require_equals = true, value_enum)]
    prequire: prequire::Prequires,
  },
  Config {
    #[command(subcommand)]
    command: ProjectConfigCommands,
  },
  Device {
    #[command(subcommand)]
    device_command: ProjectDeviceCommands,
  },
  Exec {
    #[command(subcommand)]
    action_command: ProjectActionCommands,
  },
  Image {
    #[command(subcommand)]
    image_command: ImageComands,
  },
  Magick {
    #[command(subcommand)]
    command: ImageMagickComands,
  },
  New,
  // package 将被project 取代
  Package {
    #[command(subcommand)]
    package_command: PackageComands,
  },
  Install {
    #[arg(require_equals = true, value_enum)]
    prequire: prequire::Prequires,
  },
  Ocr,
  Screen {
    #[command(subcommand)]
    screen_command: ProjectScreenCommands,
  },
  Train {
    #[command(subcommand)]
    command: ProjectTrainCommands,
  },
}

const DEFAULT_ADB_PORT: u16 = 5555;
fn validate_socket(s: &str) -> Result<String, String> {
  let pos = s.find(':');
  match pos {
    Some(pos) => {
      let _: IpAddr = s[..pos]
        .parse::<IpAddr>()
        .map_err(|_| format!("`{}` isn't a ip", &s[..pos]))?;
      let _: u16 = s[pos + 1..]
        .parse::<u16>()
        .map_err(|_| format!("`{}` isn't a port", &s[pos + 1..]))
        .unwrap();
      Ok(s.to_string())
    }
    None => {
      // validate s as ipaddress
      let _: IpAddr = s
        .parse::<IpAddr>()
        .map_err(|_| format!("`{}` isn't a ip", s))?;
      Ok(format!("{s}:{DEFAULT_ADB_PORT}"))
    }
  }
}
